iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
IT Operation

系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server系列 第 4

Day 4|把 PowerShell 查詢結果變成 CSV:開始做第一份維運報表

  • 分享至 

  • xImage
  •  

前面三天,我們已經做了幾件事情:

Day 1:為什麼系統工程師需要自動化
Day 2:PowerShell 基本操作
Day 3:Object 與 Pipeline

到目前為止,大部分結果都還停留在 PowerShell 視窗裡。

例如:

Get-Service |
Where-Object Status -eq "Running" |
Select-Object Name, DisplayName, Status

可以很快知道現在有哪些 Service 正在執行。

但實際工作不太可能把 PowerShell 視窗截圖之後直接交出去。

很多維運工作最後都會變成:

查詢資料

整理資料

產生報表

交給其他人確認

後續追蹤

例如:

AD 帳號盤點
電腦物件盤點
Server 磁碟空間
Service 狀態
使用者最後登入時間
資產清冊
異常清單

最後通常都需要一份可以用 Excel 開啟的檔案。

所以 Day 4 我們就來做第一份真正可以留下來的維運報表。

今天的目標

今天主要會用到兩個 Cmdlet:

Export-Csv
Import-Csv

最後會完成這個流程:

Get-Service

Where-Object

Select-Object

Export-Csv

CSV 報表

Excel

而且我們不只把資料輸出去。

還會試著把 CSV 再讀回 PowerShell,看看之後怎麼拿既有報表繼續做自動化。

先產生最簡單的 CSV

我們先從熟悉的 Get-Service 開始。

執行:

Get-Service |
Select-Object Name, DisplayName, Status

會看到類似:

Name DisplayName Status


Appinfo Application Information Running
BITS Background Intelligent... Stopped
EventLog Windows Event Log Running
W32Time Windows Time Running

接下來把結果輸出成 CSV。

Get-Service |
Select-Object Name, DisplayName, Status |
Export-Csv C:\Temp\Services.csv -NoTypeInformation -Encoding UTF8

如果成功,就會產生:

C:\Temp\Services.csv

使用 Excel 開啟,就可以看到:

Name DisplayName Status
Appinfo Application Information Running
BITS Background Intelligent Transfer Service Stopped
EventLog Windows Event Log Running

這已經是我們第一份 PowerShell 維運報表。

如果 C:\Temp 不存在怎麼辦?

這也是寫 Script 很常碰到的問題。

如果:

C:\Temp

不存在,Export-Csv 就會失敗。

所以正式一點的寫法,可以先確認資料夾:

$ReportPath = "C:\Temp"

if (-not (Test-Path $ReportPath)) {
New-Item -Path $ReportPath -ItemType Directory
}

這裡第一次稍微碰到 if。

意思其實很簡單:

檢查 C:\Temp

不存在?

建立 C:\Temp

其中:

Test-Path

是拿來確認路徑是否存在。

例如:

Test-Path C:\Temp

如果存在:

True

不存在:

False
Export-Csv 到底在做什麼?

回到這條:

Get-Service |
Select-Object Name, DisplayName, Status |
Export-Csv C:\Temp\Services.csv -NoTypeInformation -Encoding UTF8

可以拆成:

Get-Service

取得 Service Object

Select-Object

只留下需要的 Property

Export-Csv

把 Object 寫進 CSV

這裡最重要的其實還是前幾天一直在講的:

PowerShell 處理的是 Object。

假設一筆資料是:

Name = W32Time
DisplayName = Windows Time
Status = Running

輸出 CSV 之後,就會變成類似:

"Name","DisplayName","Status"
"W32Time","Windows Time","Running"

CSV 本質上其實就是文字檔。

只是使用:

Comma
,

分隔不同欄位。

所以 CSV 全名就是:

Comma-Separated Values

為什麼需要 -NoTypeInformation?

我們剛剛使用:

-NoTypeInformation

這個參數在 Windows PowerShell 5.1 很常看到。

目的就是不要在 CSV 開頭加入物件類型資訊。

因此我通常會寫:

Export-Csv -Path "C:\Temp\Services.csv"
-NoTypeInformation `
-Encoding UTF8

這樣產生的檔案比較乾淨,也比較適合直接拿去開。

不要把 Format-Table 接在 Export-Csv 前面

Day 3 有稍微提到這件事情,今天可以實際再看一次。

很多人第一次輸出報表會直覺寫:

Get-Service |
Format-Table Name, Status |
Export-Csv C:\Temp\Services.csv

因為在螢幕上:

Format-Table

看起來很好。

但這個概念其實不對。

Format-Table 是:

給人看的格式。

Select-Object 是:

資料本身。

所以:

Get-Service

Select-Object

Export-Csv

才是比較正確的資料處理方式。

第一次做真正的 Service 巡檢報表

接下來不要把全部 Service 都輸出去。

假設今天我們的需求是:

找出 StartType 為 Automatic,但目前不是 Running 的 Service。

可以寫:

Get-Service |
Where-Object {
$.StartType -eq "Automatic" -and
$
.Status -ne "Running"
} |
Select-Object Name, DisplayName, Status, StartType |
Export-Csv "C:\Temp\Service_Check.csv" -NoTypeInformation
-Encoding UTF8

產生:

C:\Temp\Service_Check.csv

打開後可能看到:

Name DisplayName Status StartType
MapsBroker Downloaded Maps Manager Stopped Automatic
WSearch Windows Search Stopped Automatic

不過跟 Day 3 一樣要注意:

出現在這份清單,不代表一定故障。

這份報表比較像是:

需要進一步確認的 Service 清單。

因為有些 Windows Service 本來就可能受到 Trigger Start 或其他系統機制控制。

Automation 可以先幫我們:

幾百個 Service

條件篩選

剩下幾個需要確認

而不是讓工程師從頭到尾自己找。

報表最好不要每次都叫 Services.csv

如果每天都執行:

Services.csv

第二天可能直接把第一天的報表蓋掉。

所以正式一點,我會把日期放進檔名。

PowerShell 可以取得現在時間:

Get-Date

例如:

2026年9月12日 上午 06:30:00

但這個格式不能直接拿來當檔名。

所以可以:

Get-Date -Format "yyyyMMdd"

結果:

20260912

存成變數:

$Date = Get-Date -Format "yyyyMMdd"

然後:

$FileName = "Service_Check_$Date.csv"

結果就是:

Service_Check_20260912.csv
組合完整路徑

可以再往前一步:

$Date = Get-Date -Format "yyyyMMdd"
$ReportFolder = "C:\Temp"
$ReportFile = "$ReportFolder\Service_Check_$Date.csv"

這時候:

$ReportFile

會得到:

C:\Temp\Service_Check_20260912.csv

最後:

Get-Service |
Where-Object {
$.StartType -eq "Automatic" -and
$
.Status -ne "Running"
} |
Select-Object Name, DisplayName, Status, StartType |
Export-Csv $ReportFile -NoTypeInformation -Encoding UTF8

這就開始比較像真的 Script 了。

再加上電腦名稱

如果今天只跑一台電腦,看:

Service_Check_20260912.csv

可能還知道是哪台。

但是未來如果:

Server01
Server02
Server03
Server04

同時產生報表,裡面最好直接留下 Hostname。

Windows 的 Computer Name 可以透過:

$env:COMPUTERNAME

例如:

SERVER01

問題來了。

Get-Service 裡面沒有我們想要的 ComputerName 欄位。

那要怎麼加?

可以使用:

Select-Object

建立自訂欄位。

第一次建立自訂欄位

例如:

Get-Service |
Select-Object `
@{Name="ComputerName"; Expression={$env:COMPUTERNAME}},
Name,
DisplayName,
Status

結果:

ComputerName Name DisplayName Status


SERVER01 Appinfo Application Information Running
SERVER01 BITS Background Intelligent... Stopped

這一段:

@{
Name="ComputerName"
Expression={$env:COMPUTERNAME}
}

可以先簡單理解為:

我要自己建立一個叫 ComputerName 的欄位。

而這個欄位的值是:

$env:COMPUTERNAME

後面做報表時會非常常用。

再加入報表產生時間

同樣的方法也可以加入:

CheckTime

例如:

Get-Service |
Select-Object `
@{Name="ComputerName"; Expression={$env:COMPUTERNAME}},
@{Name="CheckTime"; Expression={Get-Date}},
Name,
DisplayName,
Status,
StartType

現在資料就完整很多:

ComputerName CheckTime Name Status StartType
SERVER01 2026/09/12 06:30 EventLog Running Automatic
SERVER01 2026/09/12 06:30 BITS Stopped Manual

這時候它就不只是 PowerShell 查詢結果。

而開始比較像:

維運紀錄。

把今天內容組成第一支完整 Script

現在把前面全部串起來。

==============================

Service Health Check Report

==============================

$ReportFolder = "C:\Temp"

確認報表資料夾存在

if (-not (Test-Path $ReportFolder)) {
New-Item -Path $ReportFolder -ItemType Directory
}

建立日期

$Date = Get-Date -Format "yyyyMMdd"

建立報表路徑

$ReportFile = "$ReportFolder\Service_Check_$Date.csv"

取得 Automatic 但非 Running 的 Service

$Services = Get-Service |
Where-Object {
$.StartType -eq "Automatic" -and
$
.Status -ne "Running"
} |
Sort-Object Name |
Select-Object `
@{Name="ComputerName"; Expression={$env:COMPUTERNAME}},
@{Name="CheckTime"; Expression={Get-Date}},
Name,
DisplayName,
Status,
StartType

輸出 CSV

$Services |
Export-Csv -Path $ReportFile
-NoTypeInformation `
-Encoding UTF8

Write-Host "Report completed:"
Write-Host $ReportFile

執行之後:

Report completed:
C:\Temp\Service_Check_20260912.csv

到這裡,我們已經第一次從:

PowerShell Command

變成:

PowerShell Script

資料收集

條件篩選

整理欄位

建立報表
Export 完之後,也要會 Import

產生 CSV 只是第一步。

真正有趣的是:

CSV 還可以重新讀回 PowerShell。

例如:

$Report = Import-Csv "C:\Temp\Service_Check_20260912.csv"

然後:

$Report

就可以看到資料。

甚至可以繼續:

$Report |
Where-Object Status -eq "Stopped"

或:

$Report |
Select-Object ComputerName, Name, Status

也就是:

CSV

Import-Csv

Object

Where

Sort

Select

有沒有發現又回到 Day 3?

這就是 Object 與 Pipeline 的好處。

Import-Csv 讀進來之後又是 Object

可以實際確認:

Import-Csv "C:\Temp\Service_Check_20260912.csv" |
Get-Member

你會發現 PowerShell 又可以看到:

ComputerName
CheckTime
Name
DisplayName
Status
StartType

這表示我們可以:

$Report = Import-Csv "C:\Temp\Service_Check_20260912.csv"

然後:

$Report.Name

直接取得所有 Service Name。

不用自己處理:

逗號在哪裡?
第幾欄?
字串怎麼切?

PowerShell 已經幫我們把 CSV 轉回可以操作的 Object。

為什麼 Import-Csv 對企業維運很好用?

因為很多企業環境的資料其實都長這樣:

EmployeeID,Username,Department
24983,user01,IT
24984,user02,HR
24985,user03,Finance

例如人資提供:

離職人員.csv

我們可以:

$Users = Import-Csv "C:\Temp\DisabledUsers.csv"

接著:

foreach ($User in $Users) {
$User.Username
}

未來甚至可以變成:

HR CSV

PowerShell

找 AD User

確認帳號

停用帳號

寫 Log

產生報表

這就是為什麼我把 CSV 放得這麼前面。

因為它是企業 IT 自動化裡非常實用的資料交換方式。

CSV 不是 Excel

這邊也順便釐清一個常見觀念。

CSV ≠ Excel

CSV 只是:

文字格式的表格資料。

Excel 可以開 CSV,所以平常很容易把兩者混在一起。

CSV 沒有:

字體
顏色
多個工作表
儲存格格式
圖表
Excel Formula 格式能力

但它有一個很大的好處:

簡單。

PowerShell 很容易:

讀取
修改
過濾
輸出

所以做系統維運時,我反而非常常使用 CSV。

如果只是要:

盤點
清單
稽核
比對
匯入
匯出

CSV 通常就已經足夠。

中文變亂碼怎麼辦?

如果報表裡未來出現:

中文姓名
部門
備註
電腦名稱

Encoding 就會變得重要。

所以 Windows PowerShell 5.1 我通常會明確寫:

-Encoding UTF8

例如:

Export-Csv -Path "C:\Temp\Report.csv"
-NoTypeInformation `
-Encoding UTF8

如果使用 PowerShell 7,而 CSV 使用 Excel 開啟時遇到中文編碼問題,可以再檢查 PowerShell 版本與 CSV 編碼;新版環境也可以考慮使用適合 Excel 的 UTF-8 BOM 輸出方式。

這部分之後真的遇到再處理就好。

目前先養成:

輸出包含中文的資料時,要注意 Encoding。

今天真正學到的其實不只是 Export-Csv

到目前為止,我們已經開始把前幾天的東西串起來:

Get-Service

Object

Where-Object

篩選

Sort-Object

排序

Select-Object

整理欄位

Export-Csv

保存資料

Import-Csv

重新載入

Object

繼續處理

這套流程之後換成 Active Directory 幾乎完全一樣。

例如:

Get-ADUser

未來可以變成:

Get-ADUser

篩選 Inactive User

選 Username / LastLogonDate / Enabled

Export-Csv

AD 帳號盤點報表

所以今天看起來只是做一個:

Services.csv

其實是在建立後面 AD 自動化很重要的基礎。

Day 4 小結

今天我們正式做出了系列中的第一份維運報表。

從原本:

Get-Service

一路做到:

System Data

PowerShell Object

Filter

Sort

Select

CSV

Excel

也開始碰到幾個實際寫 Script 很重要的觀念:

Test-Path
New-Item
Get-Date
Export-Csv
Import-Csv

以及自訂欄位:

@{
Name="ComputerName"
Expression={$env:COMPUTERNAME}
}

對我來說,從今天開始 PowerShell 才比較真的進入「維運工具」的階段。

因為查詢結果不再只是執行完就消失。

我們開始可以把它:

留下來、整理、比較、交付,以及作為下一個自動化流程的輸入。


上一篇
Day 3|PowerShell Pipeline 與 Object:為什麼它比單純文字處理更適合維運?
下一篇
Day 5|PowerShell 變數、陣列與迴圈:一次處理多台 Server 的第一步
系列文
系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言